package topoMesh;

import java.util.ArrayList;
import java.util.Arrays;

import processing.core.PMatrix3D;
import processing.core.PVector;
import quickhull3d.Point3d;
import quickhull3d.QuickHull3D;

public class MeshPiping {
	
	public static TopoMesh pipe(TopoMesh mesh, int segments, float radius,
			float nodeRadius) {
		//mesh.construct();
		int[] edgeSegments = new int[mesh.getNEdges()];
		Arrays.fill(edgeSegments, segments);
		float[] edgeRadius = new float[mesh.getNEdges()];
		Arrays.fill(edgeRadius, radius);
		float[] edgeDist1 = new float[mesh.getNEdges()];
		Arrays.fill(edgeDist1, nodeRadius);
		float[] edgeDist2 = new float[mesh.getNEdges()];
		Arrays.fill(edgeDist2, nodeRadius);
		return pipe(mesh, edgeSegments, edgeRadius, edgeDist1, edgeDist2);
	}
	
	public static TopoMesh pipe(TopoMesh mesh, int[] segments,
			float[] edgeRadius,
			float nodeRadius) {
		float[] edgeDist1 = new float[mesh.getNEdges()];
		Arrays.fill(edgeDist1, nodeRadius);
		float[] edgeDist2 = new float[mesh.getNEdges()];
		Arrays.fill(edgeDist2, nodeRadius);
		return pipe(mesh, segments, edgeRadius, edgeDist1, edgeDist2);
	}
	public static TopoMesh pipe(TopoMesh mesh, int[] segments,
			float[] edgeRadius, float[] edgeDist1, float[] edgeDist2) {

		TopoMesh meshNew = new TopoMesh();

		TopoMeshNode[][] profiles1 = new TopoMeshNode[mesh.getNEdges()][];
		TopoMeshNode[][] profiles2 = new TopoMeshNode[mesh.getNEdges()][];
		for (int j = 0; j < mesh.getNNodes(); j++) {
			TopoMeshNode n = mesh.getNode(j);
			TopoMesh meshSphere = new TopoMesh();
			/*
			 * meshSphere.constructSphere(nodeSphereRadius, nodeSphereSegments,
			 * nodeSphereSegments); meshSphere.translate(n.x, n.y, n.z);
			 */
			int sumProfiles = 0;
			for (int i = 0; i < n.getEdges().size(); i++) {
				TopoMeshEdge e = n.getEdge(i);
				sumProfiles += segments[e.getId()];
			}
			Point3d[] hull = new Point3d[sumProfiles + meshSphere.getNNodes()];
			TopoMeshNode[] hullP = new TopoMeshNode[sumProfiles
					+ meshSphere.getNNodes()];
			int index = 0;

			for (int i = 0; i < n.getEdges().size(); i++) {
				TopoMeshEdge e = n.getEdge(i);
				ArrayList<PVector> profile = getProfile(edgeRadius[e.getId()],
						segments[e.getId()]);
				ArrayList<TopoMeshNode> profileNodes = new ArrayList<TopoMeshNode>();
				for (int jj = 0; jj < profile.size(); jj++) {
					TopoMeshNode p = (TopoMeshNode)profile.get(jj);
					TopoMeshNode newNode = new TopoMeshNode(p.x, p.y, p.z);
					profileNodes.add(newNode);
				}
				PVector pStart = e.getBetweenAbs(edgeDist1[i]);
				PVector pEnd = e.getBetweenAbs(e.getLength() - edgeDist2[i]);
				TopoMeshNode[][] cProfiles = profiles1;
				PVector ab = PVector.sub(pEnd, pStart);

				if (ab.y == 0 && ab.x == 0) {
					float t = ab.x;
					ab.x = -ab.z;
					ab.z = t;
				} else {
					float t = ab.y;
					ab.y = -ab.x;
					ab.x = t;
				}
				if (e.getNode1() != n) {
					PVector temp = pStart;
					pStart = pEnd;
					pEnd = temp;
					cProfiles = profiles2;
					ab.mult(-1);
				}

				PMatrix3D pm = getPMatrix(pStart, pEnd, new PVector(ab.x, ab.y,
						ab.z));
				cProfiles[e.getId()] = new TopoMeshNode[profileNodes.size()];
				for (int jj = 0; jj < profileNodes.size(); jj++) {
					PVector p = profileNodes.get(jj);
					pm.mult(p, p);
					hull[index] = new Point3d(p.x, p.y, p.z);
					TopoMeshNode newNode = new TopoMeshNode(p.x, p.y, p.z);
					
					hullP[index] = newNode;
					meshNew.addNode(newNode);
					newNode.id = i;
					cProfiles[e.getId()][jj] = newNode;
					index++;
				}
				// mesh.vertices.add(pStart);
			}

			/*for (int i = 0; i < meshSphere.getNNodes(); i++) {
				PVector p = meshSphere.getNode(i);
				hull[index] = new Point3d(p.x, p.y, p.z);
				hullP[index] = new TopoMeshNode(p.x, p.y, p.z);
				hullP[index].id = -1;
				meshNew.addNode(hullP[index]);
				index++;
			}*/
			if (hullP.length==3){
				TopoMeshFace face = new TopoMeshFace(hullP[0],hullP[1],hullP[2]);
				meshNew.addFace(face);
			}
			else if (n.getEdges().size() > 1) {
				QuickHull3D qh = new QuickHull3D();
				qh.build(hull);
				index = 0;
				int[][] indexes = qh.getFaces(QuickHull3D.POINT_RELATIVE);
				for (int i = 0; i < indexes.length; i++) {
					TopoMeshFace face = new TopoMeshFace();
					boolean partOfRings = true;
					
					for (int ii = 0; ii < indexes[i].length; ii++) {
						int vIndex = indexes[i][ii];
						TopoMeshNode nd = hullP[vIndex];
						
						if (nd.id < 0)
							partOfRings = false;
						if (nd.id != hullP[indexes[i][0]].id)
							partOfRings = false;
						face.addNode(nd);
					}
					if (!partOfRings) {
						meshNew.addFace(face);
					}
					
				}
			}

		}
		
		for (int i = 0; i < mesh.getNEdges(); i++) {
			TopoMeshEdge e = mesh.getEdge(i);
			TopoMeshNode[] ring1 = profiles1[e.getId()];
			TopoMeshNode[] ring2 = profiles2[e.getId()];
			if (ring1.length>1){
			for (int ii = 0; ii < ring1.length; ii++) {
				int ii3 = (ii + 2) % ring1.length;
				int ii2 = (ii + 1) % ring1.length;
				int ii0 = ii - 1;
				if (ii0 < 0)
					ii0 = ring1.length - 1;
				TopoMeshFace face = new TopoMeshFace();

				face.addNode(ring1[ii]);
				face.addNode(ring1[ii2]);
				// face.addNode(ring2[ii]);
				// face.addNode(ring2[ii2]);
				face.addNode(ring2[ring1.length - 1 - ii3]);
				face.addNode(ring2[ring1.length - 1 - ii2]);

				// face.inverse();
				meshNew.addFace(face);
			}
			}
			// get both profiles
			// add quads to meshNew
		}
		meshNew.rebuildIds();
		meshNew.removeUnusedAndDuplicates();
		meshNew.construct();
		for (int i = 0; i < mesh.getNEdges(); i++) {
			TopoMeshEdge e = mesh.getEdge(i);
			TopoMeshNode[] ring1 = profiles1[e.getId()];
			TopoMeshNode[] ring2 = profiles2[e.getId()];
			if (ring1.length==1){
				TopoMeshEdge eNew=new TopoMeshEdge(ring1[0],ring2[0]);
				eNew.attach();
				eNew.setId(mesh.getNEdges());
				meshNew.addEdge(eNew);
				//meshNew.addEdgeAttach(ring1[0].id, ring2[0].id);
			}
		}
		meshNew.rebuildIds();
		return meshNew;
	}

	public static PMatrix3D getPMatrix(PVector eye, PVector target, PVector up) {

		PVector zAxis = PVector.sub(target, eye);
		// println("z: "+zAxis);
		zAxis.normalize();
		PVector xAxis = up.cross(zAxis);
		xAxis.normalize();
		// println("x: "+xAxis);
		PVector yAxis = zAxis.cross(xAxis);
		// println("y: "+yAxis);
		PMatrix3D pm = new PMatrix3D();
		// xaxis.x yaxis.x zaxis.x 0
		// xaxis.y yaxis.y zaxis.y 0
		// xaxis.z yaxis.z zaxis.z 0
		// -dot(xaxis, eye) -dot(yaxis, eye) -dot(zaxis, eye) l
		pm.m00 = xAxis.x;
		pm.m01 = xAxis.y;
		pm.m02 = xAxis.z;
		pm.m03 = -PVector.dot(xAxis, eye);
		pm.m10 = yAxis.x;
		pm.m11 = yAxis.y;
		pm.m12 = yAxis.z;
		pm.m13 = -PVector.dot(yAxis, eye);
		pm.m20 = zAxis.x;
		pm.m21 = zAxis.y;
		pm.m22 = zAxis.z;
		pm.m23 = -PVector.dot(zAxis, eye);
		pm.m30 = 0;
		pm.m31 = 0;
		pm.m32 = 0;
		pm.m33 = 1;

		pm.invert();
		return pm;
	}

	public static ArrayList<PVector> getProfile(float r, int nSegments) {
		ArrayList<PVector> profile = new ArrayList<PVector>();
		float deltaAngle = (float) Math.PI * 2F / nSegments;
		float angle = 0;
		for (int i = 0; i < nSegments; i++) {
			angle += deltaAngle;
			profile.add(new TopoMeshNode(r * (float) Math.cos(angle), r
					* (float) Math.sin(angle)));
		}
		return profile;
	}

	public static TopoMesh pipe(ArrayList<PVector> nodes, int segments,
			float radius, PVector up) {
		TopoMesh pipe = new TopoMesh();
		PMatrix3D m = null;
		for (int i = 0; i < nodes.size() - 1; i++) {
			PVector n1 = nodes.get(i);
			PVector n2 = null;
			if (i == nodes.size() - 1) {
				n2 = nodes.get(i - 2);
				n2.x = n1.x + (n1.x - n2.x);
				n2.y = n1.y + (n1.y - n2.y);
				n2.x = n1.z + (n1.z - n2.z);
			} else {
				n2 = nodes.get(i + 1);
			}
			m = getPMatrix(n1, n2, up);
			ArrayList<PVector> profile = getProfile(radius, segments);
			for (int j = 0; j < profile.size(); j++) {
				PVector p = profile.get(j);
				m.mult(p, p);
				pipe.addNode(p.x, p.y, p.z);
			}
		}
		for (int i = 0; i < pipe.nodes.size() - segments; i += segments) {
			for (int j = 0; j < segments; j++) {
				int j2 = (j + 1) % segments;
				pipe.addQuad(i + j, i + j + segments, i + j2 + segments, i + j2);
			}
		}
		return pipe;
	}
}// creates the edgeprofile

